/*
 * Copyright (c) 2011 Norbert Kopcsek
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.modulr.bundle {

	import flash.display.Loader;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.system.Capabilities;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.getDefinitionByName;

	import mx.events.ModuleEvent;
	import mx.modules.IModuleInfo;

	[Event( name="complete", type="flash.events.Event" )]
	[Event( name="error", type="flash.events.ErrorEvent" )]
	/**
	 * Responsible for loading a single bundle.
	 * Throw away objects, no need to clean up event listeners inside this class.
	 *
	 * @see org.modulr.bundle.Bundle
	 *
	 * @author Norbert Kopcsek
	 */
	public class BundleLoader extends EventDispatcher {


		//------------------------------------------------------------------------------
		//
		//   Constructor 
		//
		//------------------------------------------------------------------------------

		public function BundleLoader( bundle:Bundle, domain:ApplicationDomain ) {
			_bundle = bundle;
			_domain = domain
		}


		//------------------------------------------------------------------------------
		//
		//   Properties (Getter/Setter) 
		//
		//------------------------------------------------------------------------------

		//--------------------------------------
		// bundle 
		//--------------------------------------

		/**
		 * The bundle provided in the constructor.
		 */
		public function get bundle():Bundle {
			return _bundle;
		}


		//------------------------------------------------------------------------------
		//
		//   Protected fields 
		//
		//------------------------------------------------------------------------------

		/**
		 * @private
		 */
		protected var _bundle:Bundle;

		/**
		 * @private
		 */
		protected var _domain:ApplicationDomain;

		/**
		 * @private
		 * URLLoader used to load the SWF file.
		 */
		private var urlLoader:URLLoader;

		/**
		 * @private
		 * Loader used to load byte arrays of runtime shared libraries.
		 */
		private var loader:Loader;

		/**
		 * @private
		 * ByteArray containing the loaded SWF file.
		 */
		private var bytes:ByteArray;


		//------------------------------------------------------------------------------
		//
		//  Methods 
		//
		//------------------------------------------------------------------------------

		/**
		 * Loads the bundle provided in the constructor.
		 */
		public function load():void {
			/*if( inAIR() ) {
			   loadForAIR();
			 } else {*/
			loadForFlashPlayer();
		/*}*/
		}


		//------------------------------------------------------------------------------
		//
		//   Protected methods 
		//
		//------------------------------------------------------------------------------

		/**
		 * @private
		 */
		/*protected function loadForAIR():void {
		   var fileClass:Object = getDefinitionByName("flash.filesystem.File") as Class;
		   var file:Object = fileClass.applicationDirectory.resolvePath( bundle.url );
		   var fileStreamClass:Class = getDefinitionByName("flash.filesystem.FileStream") as Class;
		   var fileStream:Object = new fileStreamClass();
		   fileStream.open(file,"read");
		   bytes = new ByteArray;
		   fileStream.readBytes(bytes);
		   fileStream.close();
		   loadBundle();
		 }*/

		/**
		 * @private
		 */
		protected function loadForFlashPlayer():void {
			urlLoader = new URLLoader();
			urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
			urlLoader.addEventListener( Event.COMPLETE, urlLoader_completeHandler ); // no weak reference
			urlLoader.addEventListener( IOErrorEvent.IO_ERROR, urlLoader_ioErrorHandler ); // no weak reference
			urlLoader.addEventListener( SecurityErrorEvent.SECURITY_ERROR, urlLoader_securityErrorHandler ); // no weak reference
			urlLoader.load( new URLRequest( bundle.url ));
		}

		/**
		 * @private
		 */
		protected function loadBundle():void {
			if ( bundle.isRsl ) {
				loadBundleAsRsl();
			} else {
				loadBundleAsModule();
			}
		}

		/**
		 * @private
		 */
		protected function loadBundleAsModule():void {
			addEventListenersToModuleInfo( bundle.moduleInfo );
			bundle.moduleInfo.data = bundle;
			bundle.moduleInfo.load( _domain ? _domain : ApplicationDomain.currentDomain, null, bytes );
		}

		/**
		 * @private
		 */
		protected function loadBundleAsRsl():void {

			var loaderContext:LoaderContext = new LoaderContext( false, _domain ? _domain : ApplicationDomain.currentDomain );

			if ( inAIR()) {
				loaderContext[ "allowLoadBytesCodeExecution" ] = true;
			}
			loader = new Loader();
			loader.contentLoaderInfo.addEventListener( Event.COMPLETE, loader_completeHandler );
			loader.loadBytes( bytes, loaderContext );
		}

		/**
		 * @private
		 */
		protected function addEventListenersToModuleInfo( moduleInfo:IModuleInfo ):void {
			moduleInfo.addEventListener( ModuleEvent.READY, module_readyHandler ); // no weak reference
			moduleInfo.addEventListener( ModuleEvent.ERROR, module_errorHandler ); // no weak reference	
		}

		/**
		 * @private
		 */
		protected function removeEventListenersFromModuleInfo( moduleInfo:IModuleInfo ):void {
			moduleInfo.removeEventListener( ModuleEvent.READY, module_readyHandler );
			moduleInfo.removeEventListener( ModuleEvent.ERROR, module_errorHandler );
		}

		/**
		 * @private
		 * Detects the AIR runtime.
		 */
		protected function inAIR():Boolean {
			return Capabilities.playerType == "Desktop";
		}



		//--------------------------------------------------------------------------
		//
		//  Event listeners
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		protected function urlLoader_completeHandler( event:Event ):void {
			bytes = urlLoader.data as ByteArray;
			// TODO Save bundle size to bundle 
			trace( "Bytes loaded for bundle " + bundle.bundleId + ": " + bytes.length );
			loadBundle();
		}

		/**
		 * @private
		 */
		protected function urlLoader_ioErrorHandler( event:IOErrorEvent ):void {
			dispatchEvent( new ErrorEvent( ErrorEvent.ERROR ));
		}

		/**
		 * @private
		 */
		protected function urlLoader_securityErrorHandler( event:SecurityErrorEvent ):void {
			dispatchEvent( new ErrorEvent( ErrorEvent.ERROR ));
		}

		/**
		 * @private
		 * Called when a module has been loaded and is ready to be activated.
		 */
		protected function module_readyHandler( event:ModuleEvent ):void {
			var moduleInfo:IModuleInfo = event.module as IModuleInfo;
			removeEventListenersFromModuleInfo( moduleInfo );

			dispatchEvent( new Event( Event.COMPLETE ));
		}

		/**
		 * @private
		 */
		protected function module_errorHandler( event:ModuleEvent ):void {
			var moduleInfo:IModuleInfo = event.module as IModuleInfo;
			removeEventListenersFromModuleInfo( moduleInfo );

			if ( event.errorText.indexOf( "SWF" ) >= 0 ) {
				// Contains "SWF is not a loadable module" its not a module module and should be reloaded using the Loader
				bundle.clearModuleInfo();
				bundle.isRsl = true;
				loadBundleAsRsl();
			} else /* if( event.errorText.indexOf( "#1001" ) >= 0 )*/ {
				// Failed to match the digest ...
				dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, event.errorText ));
			}
		}

		/**
		 * @private
		 */
		protected function loader_completeHandler( event:Event ):void {
			dispatchEvent( new Event( Event.COMPLETE ));
		}
	}
}

